package org.snackframework.core;


import java.io.FileInputStream;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.Properties;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.Template;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.context.Context;
import org.snackframework.constants.ServletConstants;
import org.snackframework.mapping.DefaultAnnotationMapping;
import org.snackframework.mapping.DefaultModuleMapping;
import org.snackframework.mapping.Mapping;
import org.snackframework.util.PathUtils;
import org.springframework.beans.BeansException;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.FrameworkServlet;

public class DispatcherServlet extends FrameworkServlet
{
	/**
	 * configuration file path, if have more than one configuration file, it separates by comma
	 */
	private String[] confPath;
	
	/**
	 * serial version ID
	 */
	private static final long serialVersionUID = 5930767558892136510L;

	private static Log log = LogFactory.getLog(DispatcherServlet.class);
	
	private static ViewClassLoader viewClassLoader=new ViewClassLoader(DispatcherServlet.class.getClassLoader());
	
	private static Mapping moduleMapping = new DefaultModuleMapping(viewClassLoader);
	
	private static Mapping annotationMapping = new DefaultAnnotationMapping(viewClassLoader);
	
	/**
	 * 所有配置的属性都在其中
	 */
	private static Properties props=new Properties();
	
	private static String DEFAULT_DATE_PATTERN="yyyyMMdd";
	
	@Override
	protected void doService(HttpServletRequest req, HttpServletResponse resp)
			throws Exception
	{
//		System.out.println("doService...");
		
		//analyse request servlet path
		String relativePath=analyseRequestUrl(req);
		
		//dispatcher 
		dispatcher(relativePath,req,resp);
		
		
	}

	protected String analyseRequestUrl(HttpServletRequest req)
	{
		return req.getServletPath();
	}
	
	@SuppressWarnings("unchecked")
	protected void dispatcher(String relativePath,HttpServletRequest req,HttpServletResponse resp)
	{
		try
		{
			// annotation mapping first
			ThroughUrlCacheElement throughUrlCacheElement=(ThroughUrlCacheElement)annotationMapping.get(relativePath);
			if(throughUrlCacheElement!=null)
			{
				Object[] args=new Object[throughUrlCacheElement.getMethodMeta().getParams().length];
				
				// get request parameter ; method parameter
				for(int i=0;i<throughUrlCacheElement.getMethodMeta().getParams().length;i++)
				{
					//TODO 考虑后期扩展，参数支持annotation的方式提供额外方便的配置！！！
					ParameterMeta parameterMeta=throughUrlCacheElement.getMethodMeta().getParams()[i];
					if(parameterMeta.getType().getName().equals("javax.servlet.http.HttpServletRequest"))
					{
						args[i]=req;
					}
					else if(parameterMeta.getType().getName().equals("javax.servlet.http.HttpServletResponse"))
					{
						args[i]=resp;
					}
					else
					{
						String paramValue=req.getParameter(parameterMeta.getParameterName());
	
						args[i]=convert(paramValue,parameterMeta.getType());
					}
				}
				
				//set accessible
				if(throughUrlCacheElement.getMethodMeta().getModifiers()==Modifier.PRIVATE)
					throughUrlCacheElement.getMethodMeta().getMethod().setAccessible(true);
				
				
					//invoke
					Object ret;
					if(throughUrlCacheElement.isSingleton())
					{
						if(!throughUrlCacheElement.isInstantiated())
						{
							throughUrlCacheElement.setViewObject();
						}
						ret=throughUrlCacheElement.getMethodMeta().getMethod().invoke(throughUrlCacheElement.getViewMetaData().getViewObject(), args);
					}
					else
						ret=throughUrlCacheElement.getMethodMeta().getMethod().invoke(throughUrlCacheElement.getViewMetaData().prototypeInstance(), args);
					
					// if return type is not void,then merge the specified template
					ModelView modelView;
					if(ret!=null)
					{
						modelView=(ModelView)ret;
						String view=(String)modelView.getView();
						String direc=PathUtils.getTemplateDirectory(view);
						String absoluteDirec=req.getSession().getServletContext().getRealPath(direc);
						String templateName=PathUtils.getTemplateName(view);
						
						// merge template context
						merge(absoluteDirec,templateName,modelView.getModel(),resp.getWriter());
					}
					//TODO: 没有返回 ModelView 的情况，其后还可以进行一些后置的处理
					else
					{
					}
					
					//TODO 后置处理
					
				
			}
			// 没有annotation mapping时,then module mapping
			else
			{
				ScreenCacheElement screenCacheElement=null;
				if(relativePath.contains("."))
				{
					String key=relativePath.split("\\.")[0];
					screenCacheElement=(ScreenCacheElement)moduleMapping.get(key);
				}
				else
				{
					screenCacheElement=(ScreenCacheElement)moduleMapping.get(relativePath);
				}
				
				// 生成screen对应处理类的实例
				Object instance=null;
				if(screenCacheElement.isSingleton())
				{
					if(!screenCacheElement.isInstantiated())
					{
						synchronized(screenCacheElement)
						{
							if(!screenCacheElement.isInstantiated())
							{
								screenCacheElement.createSingletonInstance();
							}
						}
					}
					instance=screenCacheElement.getObject();
				}
				// 非singleton的
				else
				{
					instance=screenCacheElement.getClazz().newInstance();
				}
				
				// 属性注入
				if(!screenCacheElement.isInitProperty())
				{
					WebApplicationContext webApplicationContext=WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
					
					// 考虑了存在类继承关系的层次结构 !!!!
					((DefaultModuleMapping)moduleMapping).initProperty(instance, screenCacheElement.getClazz(), webApplicationContext);
					
					if(screenCacheElement.isSingleton())
					{
						screenCacheElement.finishInitProperty();
					}
				}
				
				// invoke execute method
				ModelMap context=new ModelMap();
				Method method=screenCacheElement.getClazz().getMethod("execute", new Class[]{javax.servlet.http.HttpServletRequest.class,javax.servlet.http.HttpServletResponse.class,ModelMap.class});
				method.invoke(instance, new Object[]{req,resp,context});
				
				String direc=PathUtils.getTemplateDirectory(screenCacheElement.getScreen());
				String absoluteDirec=req.getSession().getServletContext().getRealPath(direc);
				String templateName=PathUtils.getTemplateName(screenCacheElement.getScreen());
				
				// merge template context
				merge(absoluteDirec,templateName,context,resp.getWriter());
				
				//TODO 后置处理
			}
		}
		catch(NoSuchMethodException e)
		{
			e.printStackTrace();
		}
		catch(InstantiationException e)
		{
			e.printStackTrace();
		}
		catch(IllegalArgumentException e)
		{
			e.printStackTrace();
		}
		catch(IllegalAccessException e)
		{
			e.printStackTrace();
		}
		catch(InvocationTargetException e)
		{
			System.out.println(e.getCause());
			e.printStackTrace();
		}
		catch(IOException e)
		{
			e.printStackTrace();
		}
	}
	
	protected void merge(String absoluteDirec,String templateName,Context context,Writer writer)
	{
		Velocity.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, absoluteDirec);
		try
		{
			Velocity.init();
			String suffix=props.getProperty(ServletConstants.SUFFIX_OF_SCREEN);
			Template template=Velocity.getTemplate(templateName+(suffix==null ? ".vm" : "."+suffix));
			template.merge(context, writer);
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
	/**
	 * 参数类型转换  
	 */
	//FIXME need refactor
	protected Object convert(String strValue,Class type)
	{
		if(type.getName().equals("java.lang.String"))
			return strValue;
		else if(type.getName().equals("java.lang.Integer"))
		{
			if(null!=strValue)
				return Integer.valueOf(strValue);
			else
				return null;
		}
		else if(type.getName().equals("int"))
		{
			if(null!=strValue)
				return Integer.valueOf(strValue).intValue();
			else
				return 0;  //返回默认值
		}
		else if(type.getName().equals("java.lang.Float"))
		{
			if(null!=strValue)
				return Float.valueOf(strValue);
			else
				return 0.0;
		}
		else if(type.getName().equals("float"))
		{
			if(null!=strValue)
				return Float.valueOf(strValue).floatValue();
			else
				return 0.0;
		}
		else if(type.getName().equals("java.lang.Long"))
		{
			if(null!=strValue)
				return Long.valueOf(strValue);
			else
				return 0L;
		}
		else if(type.getName().equals("long"))
		{
			if(null!=strValue)
				return Long.valueOf(strValue).longValue();
			else
				return 0L;
		}
		else if(type.getName().equals("java.lang.Double"))
		{
			if(null!=strValue)
				return Double.valueOf(strValue);
			else
				return 0D;
		}
		else if(type.getName().equals("double"))
		{
			if(null!=strValue)
				return Double.valueOf(strValue).doubleValue();
			else
				return 0D;
		}
		//FIXME 提供可配置的时间转换方式
		else if(type.getName().equals("java.util.Date"))
		{
			String pattern=props.getProperty("dateFormatPattern");
			if(null==pattern)
				pattern=DEFAULT_DATE_PATTERN;
			
			SimpleDateFormat sdf=new SimpleDateFormat(pattern);
			Date date=null;
			try
			{
				date=sdf.parse(strValue);
			}
			catch(ParseException e)
			{
				e.printStackTrace();
			}
			return date;
		}
		
		//TODO 自定义类型的处理
		else
			return null;
	}
	
	/**
	 * initialize urlHandlerMapping,
	 */
	@Override
	protected void initFrameworkServlet() throws ServletException,
			BeansException
	{
		log.info("initFrameworkServlet ...");
		
		// load framework class
		loadFrameworkClass();
		
		// handler initial parameter
		analyseInitParameter();
		
		//FIXME load resource
		initHandlerMapping(confPath);
	}

	protected void analyseInitParameter()
	{
		String initParameter = this.getServletConfig().getInitParameter(ServletConstants.SERVLET_INIT_PARAMETER);
		String[] segment = initParameter.split(",");
		if(ArrayUtils.isEmpty(segment))
		{
			log.error("DispatcherServlet 启动参数 dispatcherServletInitialParameter 没有配置");
			throw new RuntimeException("DispatcherServlet 启动需要初始化参数, 请配置初始化参数");
		}
		else
		{
			confPath=new String[segment.length];
			for(int i=0;i<segment.length;i++)
			{
				confPath[i]=segment[i];
			}
		}
		log.info("dispatcherServletInitialParameter 启动参数的值为:"+confPath);
	}
	
	protected void initHandlerMapping(String[] confs)
	{
		String path=this.getServletContext().getContextPath();
		
		log.info("DispatcherServlet load source from contextPath : "+path);
		
		for(String conf:confs)
		{
			// load *.properties
			if(-1!=conf.indexOf(".properties"))
			{
				String realPath;
				int index=conf.indexOf("classpath:");
				
				//resource file 在 classpath 下面
				if(-1!=index)
				{
					index=conf.indexOf(':');
					String tempPath=conf.substring(index+1);
					String[] segments=StringUtils.split(tempPath, '/');
					LinkedList<String> list=new LinkedList<String>();
					for(String seg:segments)
					{
						if(StringUtils.isNotBlank(seg))
							list.add(seg);
					}
					int size=list.size();
					if(0==size)
					{
						log.error("DispatcherServlet 启动参数 dispatcherServletInitialParameter 配置 classpath:/有错");
						throw new RuntimeException("classpath:/ 配置有错");
					}
					
					String resourceName=list.getLast();
					String pack="";
					if(size>1)
					{
						StringBuffer buffer=new StringBuffer();
						for(int i=0;i<size-1;i++)
						{
							buffer.append(list.get(i)+".");
						}
						pack=StringUtils.removeEnd(buffer.toString(), ".");
					}
					realPath=PathUtils.getResourcePath(getServletContext(), pack, resourceName);
				}
				
				// resource file 在 web 目录下面
				else
				{
					realPath=PathUtils.getResourcePath(getServletContext(), null, conf);
				}
				
				try
				{
					props.load(new FileInputStream(realPath));
				}
				catch(IOException e)
				{
					log.error("load "+conf+" error", e);
				}
				
				String aPacks = (String)props.get(ServletConstants.ANNOTATION_SCAN_DIRECTORY);
				if(StringUtils.isNotBlank(aPacks))
				{
					annotationMapping.setPackages(aPacks.split(","));
				}
				
				String mPacks = (String)props.get(ServletConstants.MODULE_SCAN_DIRECTORY);
				if(StringUtils.isNotBlank(mPacks))
				{
					moduleMapping.setPackages(mPacks.split(","));
				}
				
				annotationMapping.initMapping(this.getServletContext());
				moduleMapping.initMapping(this.getServletContext());
				// module 是否 lazy
				if(props.getProperty(ServletConstants.LAZY_INIT_MODULE)!="true")
				{
					//TODO 实例化moduleMapping里面所有模板对应的class
					((DefaultModuleMapping)moduleMapping).instanceAllSingletonModule(this.getServletContext());
				}
				else{}
			}
		}
	}
	
	// FIXME 需要重构 有没有什么别的方式读取 snack-core 中的类文件 ,避免直接在代码中把jar文件名,类名写死
	protected void loadFrameworkClass()
	{
		String jarName="snack-core-1.0.0.jar";
		String jarPath=PathUtils.getJarPath(this.getServletContext(), jarName);
		
//		viewClassLoader.loadFrameworkClass(new String[]{"org/snackframework/annotation/Controller.class","org/snackframework/annotation/ControllerType.class","org/snackframework/annotation/UrlMapping.class"}, jarPath);
	
		viewClassLoader.loadFrameworkClass(new String[]{jarPath});
		
		if(log.isInfoEnabled())
		{
			System.out.println(viewClassLoader.getParent());
			ClassLoader cl=moduleMapping.getClass().getClassLoader();
			System.out.println(cl);
			while(null!=(cl=cl.getParent()))
			{
				System.out.println(cl);
			}
		}
	}
	
	public String[] getConfPath()
	{
		return confPath;
	}

	public void setConfPath(String[] confPath)
	{
		this.confPath = confPath;
	}
}
